﻿//==============================================================================
// File: MultiMap.cs
// Created: 2010-05-10
// Author: Piotr Włodek
//==============================================================================
// This file is a part of MAMMOTH project.
// Copyright (C) 2010 AGH University of Science and Technology, Krakow.
// https://caribou.iisg.agh.edu.pl/trac/mammoth
//
// Mammoth is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// 
// Mammoth is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU Lesser General Public License for more details.
// 
// You should have received a copy of the GNU Lesser General Public License
// along with WpfRcp. If not, see http://www.gnu.org/licenses/.
//==============================================================================

using System;
using System.Collections;
using System.Collections.Generic;

namespace WpfRcp.PropertyModel.Collections
{
    /// <summary>
    /// A data structure that relates multiple values to each key.
    /// </summary>
    /// <typeparam name="TKey">The key type.</typeparam>
    /// <typeparam name="TValue">The value type.</typeparam>
    public class MultiMap<TKey, TValue> : IMultiMap<TKey, TValue>, IUnsafeMultiMap<TKey, TValue>
    {
        #region Fields

        private readonly Dictionary<TKey, ICollection<TValue>> m_Items = new Dictionary<TKey, ICollection<TValue>>();
        private readonly Func<ICollection<TValue>> m_CollectionFactoryMethod;

        #endregion

        #region Constructors

        /// <summary>
        /// Initializes new instance of the <see cref="MultiMap{TKey,TValue}"/> class.
        /// </summary>
        public MultiMap() : this(() => new List<TValue>())
        {
        }

        /// <summary>
        /// Initializes new instance of the <see cref="MultiMap{TKey,TValue}"/> class.
        /// </summary>
        /// <param name="collectionFactoryMethod">Factory method used every time a new collection is required.</param>
        public MultiMap(Func<ICollection<TValue>> collectionFactoryMethod)
        {
            if (collectionFactoryMethod == null)
                throw new ArgumentNullException("collectionFactoryMethod");

            m_CollectionFactoryMethod = collectionFactoryMethod;
        }

        /// <summary>
        /// Initializes new instance of the <see cref="MultiMap{TKey,TValue}"/> class.
        /// </summary>
        /// <param name="initializationKeys">A collection of initial keys.</param>
        /// <param name="collectionFactoryMethod">Factory method used every time a new collection is required.</param>
        public MultiMap(IEnumerable<TKey> initializationKeys, Func<ICollection<TValue>> collectionFactoryMethod)
        {
            if (initializationKeys == null)
                throw new ArgumentNullException("initializationKeys");

            if (collectionFactoryMethod == null)
                throw new ArgumentNullException("collectionFactoryMethod");

            m_CollectionFactoryMethod = collectionFactoryMethod;

            foreach (var key in initializationKeys)
            {
                m_Items.Add(key, collectionFactoryMethod());
            }
        }

        #endregion

        #region Properties

        /// <summary>
        /// Gets the values associated with the specified key.
        /// </summary>
        /// <param name="key">The requested key.</param>
        /// <returns>A list of values associated with the key.</returns>
        public ICollection<TValue> this[TKey key]
        {
            get
            {
                if (!m_Items.ContainsKey(key))
                    throw new KeyNotFoundException("The specified key does not exist in the MultiMap.");

                return m_Items[key];
            }
        }

        /// <summary>
        /// Gets a collection containing the keys in the Multimap.
        /// </summary>
        public ICollection<TKey> Keys
        {
            get { return m_Items.Keys; }
        }

        /// <summary>
        /// Gets a collection containing the lists of values in the Multimap.
        /// </summary>
        public IEnumerable<ICollection<TValue>> Values
        {
            get { return m_Items.Values; }
        }

        #endregion

        #region Public Methods

        /// <summary>
        /// Adds an item.
        /// </summary>
        /// <param name="key">The key to store the value under.</param>
        /// <param name="value">The value to add.</param>
        public void Add(TKey key, TValue value)
        {
            if (!m_Items.ContainsKey(key))
                m_Items.Add(key, m_CollectionFactoryMethod());

            m_Items[key].Add(value);
        }

        /// <summary>
        /// Removes the specified item.
        /// </summary>
        /// <param name="key">The key the item is stored under.</param>
        /// <param name="value">The value to remove.</param>
        /// <returns><see langword="True"/> if the item was removed successfully, otherwise <see langword="false"/>.</returns>
        public bool Remove(TKey key, TValue value)
        {
            if (!m_Items.ContainsKey(key))
                return false;

            return m_Items[key].Remove(value);
        }

        /// <summary>
        /// Removes all items stored under the specified key.
        /// </summary>
        /// <param name="key">The key whose items should be removed.</param>
        /// <returns><see langword="True"/> if the item was removed successfully, otherwise <see langword="false"/>.</returns>
        public bool RemoveAll(TKey key)
        {
            return m_Items.Remove(key);
        }

        /// <summary>
        /// Removes all items stored in the collection.
        /// </summary>
        public void Clear()
        {
            m_Items.Clear();
        }

        /// <summary>
        /// Clears all underlying collections without removing them.
        /// </summary>
        public void ClearValues()
        {
            foreach (var value in Values)
            {
                value.Clear();
            }
        }

        /// <summary>
        /// Determines if any items are stored under the specified key.
        /// </summary>
        /// <param name="key">The key in question.</param>
        /// <returns><see langword="True"/> if there are items stored under the specified key, otherwise <see langword="false"/>.</returns>
        public bool ContainsKey(TKey key)
        {
            return m_Items.ContainsKey(key);
        }

        /// <summary>
        /// Determines if the specified value is stored in the collection.
        /// </summary>
        /// <param name="key">The key the value should be stored under.</param>
        /// <param name="value">The value in question.</param>
        /// <returns><see langword="True"/> if the item exists in the collection, otherwise <see langword="false"/>.</returns>
        public bool ContainsValue(TKey key, TValue value)
        {
            return (m_Items.ContainsKey(key) && m_Items[key].Contains(value));
        }

        #endregion

        #region Functor Methods

        /// <summary>
        /// Applies the specified action to each key/value pair of the multimap.
        /// </summary>
        /// <param name="action">The action to apply.</param>
        public void ForEach(Action<TKey, ICollection<TValue>> action)
        {
            foreach (var pair in m_Items)
                action(pair.Key, pair.Value);
        }

        #endregion

        #region IEnumerable Implementation

        IEnumerator<KeyValuePair<TKey, ICollection<TValue>>> IEnumerable<KeyValuePair<TKey, ICollection<TValue>>>.GetEnumerator()
        {
            foreach (KeyValuePair<TKey, ICollection<TValue>> pair in m_Items)
                yield return pair;
        }

        /// <summary>
        /// Returns an enumerator that iterates through the <see cref="MultiMap{TKey,TValue}"/>.
        /// </summary>
        public IEnumerator GetEnumerator()
        {
            foreach (KeyValuePair<TKey, ICollection<TValue>> pair in m_Items)
                yield return pair;
        }

        #endregion

        #region IUnsafeMultiMap<TKey, TValue> Implementation

        /// <summary>
        /// Adds an item. This method does not check if the given key is present
        /// in the MultiMap. If the key is not present, an exception is thrown.
        /// </summary>
        /// <param name="key">The key to store the value under.</param>
        /// <param name="value">The value to add.</param>
        void IUnsafeMultiMap<TKey, TValue>.UnsafeAdd(TKey key, TValue value)
        {
            m_Items[key].Add(value);
        }

        /// <summary>
        /// Removes the specified item. This method does not check if the given key is present
        /// in the MultiMap. If the key is not present, an exception is thrown.
        /// </summary>
        /// <param name="key">The key the item is stored under.</param>
        /// <param name="value">The value to remove.</param>
        /// <returns><see langword="True"/> if the item was removed successfully, otherwise <see langword="false"/>.</returns>
        bool IUnsafeMultiMap<TKey, TValue>.UnsafeRemove(TKey key, TValue value)
        {
            return m_Items[key].Remove(value);
        }

        /// <summary>
        /// Gets the values associated with the specified key. This method does
        /// not check if the given key is present in the MultiMap. If the key
        /// is not present, an exception is thrown.
        /// </summary>
        /// <param name="key">The requested key.</param>
        /// <returns>A list of values associated with the key.</returns>
        ICollection<TValue> IUnsafeMultiMap<TKey, TValue>.UnsafeGet(TKey key)
        {
            return m_Items[key];
        }

        #endregion
    }
}